﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using System.ComponentModel;
using XMas;


namespace XMasContent
{
    /// <summary>
    /// Custom processor extends the builtin framework ModelProcessor class,
    /// adding animation support.
    /// </summary>
    [ContentProcessor(DisplayName = "XMAS - FBXProcess")]
    public class SkinnedModelProcessor : ModelProcessor
    {
        #region Fields

        CSkeletonData skeleton_data;
        string directory;
        private bool bIncludeShader = true;
        
        #endregion


        #region Properties

        [DisplayName("Include shader and texture")]
        [Description("Set to true if you can include shader and texture into model automatly.")]
        [DefaultValue(true)]
        [Browsable(true)]
        public bool IncludeShader
        {
            get { return bIncludeShader; }
            set { bIncludeShader = value; }
        }

        #endregion


        /// <summary>
        /// The main Process method converts an intermediate format content pipeline
        /// NodeContent tree to a ModelContent object with embedded animation data.
        /// </summary>
        public override ModelContent Process(NodeContent input,
                                             ContentProcessorContext context)
        {
            //System.Diagnostics.Debugger.Launch();
            XmasContent.GetInstance().SetContextProcessor(context);
            skeleton_data = new CSkeletonData();
            if (input == null)
                throw new ArgumentNullException("input");
            ValidateMesh(input, context, null);

            // Load material
            directory = Path.GetDirectoryName(input.Identity.SourceFilename);
            //if (bIncludeShader)
            //    loadTextureModel(input);

            //Bind skeleton
            BoneContent bind_skeleton = MeshHelper.FindSkeleton(input);
            ModelContent model = base.Process(input, context);
            XmasContent.GetInstance().warn("\n\nBind skeleton:", false);
            IList<BoneContent> bind_bones = MeshHelper.FlattenSkeleton(bind_skeleton);
            foreach (BoneContent bone in bind_bones)
            {
                skeleton_data.bind_skeleton.Add(bone.Transform);
                skeleton_data.inverse_bind_skeleton.Add(Matrix.Invert(bone.AbsoluteTransform));
            }



            // TODO: Parse anims
            /*
            string[] anim_files = Directory.GetFiles(directory, "*.caf");
            for (int i = 0; i < anim_files.Length; ++i)
            {
                CCal3DAnimParser anim_parser = new CCal3DAnimParser();
                anim_parser.load(anim_files[i], context, skeleton_data, bind_skeleton);
            }
             * 
             *        // Convert animation data to our runtime format.
            Dictionary<string, AnimationClip> animationClips;
            animationClips = ProcessAnimations(skeleton.Animations, bones);
            */

            // Add animations
            FBXAnimParser animParser = new FBXAnimParser();
            animParser.ParseAllAnimations(skeleton_data, bind_bones, bind_skeleton.Animations);


            model.Tag = skeleton_data;
            return model;
        }


        

        /// <summary>
        /// Makes sure this mesh contains the kind of data we know how to animate.
        /// </summary>
        static void ValidateMesh(NodeContent node, ContentProcessorContext context,
                                 string parentBoneName)
        {
            MeshContent mesh = node as MeshContent;

            if (mesh != null)
            {
                // Validate the mesh.
                if (parentBoneName != null)
                {
                    context.Logger.LogWarning(null, null,
                        "Mesh {0} is a child of bone {1}. SkinnedModelProcessor " +
                        "does not correctly handle meshes that are children of bones.",
                        mesh.Name, parentBoneName);
                }

                if (!MeshHasSkinning(mesh))
                {
                    context.Logger.LogWarning(null, null,
                        "Mesh {0} has no skinning information, so it has been deleted.",
                        mesh.Name);

                    mesh.Parent.Children.Remove(mesh);
                    return;
                }
            }
            else if (node is BoneContent)
            {
                // If this is a bone, remember that we are now looking inside it.
                parentBoneName = node.Name;
            }

            // Recurse (iterating over a copy of the child collection,
            // because validating children may delete some of them).
            foreach (NodeContent child in new List<NodeContent>(node.Children))
                ValidateMesh(child, context, parentBoneName);
        }


        /// <summary>
        /// Checks whether a mesh contains skininng information.
        /// </summary>
        static bool MeshHasSkinning(MeshContent mesh)
        {
            foreach (GeometryContent geometry in mesh.Geometry)
            {
                if (!geometry.Vertices.Channels.Contains(VertexChannelNames.Weights()))
                    return false;
            }

            return true;
        }


        
        /// <summary>
        /// Changes all the materials to use our skinned model effect.
        /// </summary>
        protected override MaterialContent ConvertMaterial(MaterialContent material,
                                                        ContentProcessorContext context)
        {
            if (!bIncludeShader)
                return base.ConvertMaterial(material, context);

     
            //return XmasContent.GetInstance().GetSkinningMaterial(directory, material, context);
            
            BasicMaterialContent basicMaterial = material as BasicMaterialContent;
            if (basicMaterial == null)
            {
                throw new InvalidContentException(string.Format(
                    "SkinnedModelProcessor only supports BasicMaterialContent, " +
                    "but input mesh uses {0}.", material.GetType()));
            }

            EffectMaterialContent effectMaterial = new EffectMaterialContent();

            // Store a reference to our skinned mesh effect.
            string effectPath = Path.GetFullPath("SkinnedModel.fx");

            effectMaterial.Effect = new ExternalReference<EffectContent>(effectPath);

            // Copy texture settings from the input
            // BasicMaterialContent over to our new material.
            if (basicMaterial.Texture != null)
                effectMaterial.Textures.Add("Texture", basicMaterial.Texture);

            // Chain to the base ModelProcessor converter.
            return base.ConvertMaterial(effectMaterial, context);
             
        }
    }
}
